package com.github.aesteve.vertx.nubes.handlers.impl;

import com.github.aesteve.vertx.nubes.Config;
import com.github.aesteve.vertx.nubes.exceptions.params.WrongParameterException;
import com.github.aesteve.vertx.nubes.handlers.AbstractMethodInvocationHandler;
import com.github.aesteve.vertx.nubes.marshallers.Payload;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.ext.web.RoutingContext;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.function.BiConsumer;

public class DefaultMethodInvocationHandler<T> extends AbstractMethodInvocationHandler<T> {

  private static final Logger LOG = LoggerFactory.getLogger(DefaultMethodInvocationHandler.class);

  public DefaultMethodInvocationHandler(Object instance, Method method, Config config, boolean hasNext, BiConsumer<RoutingContext, T> returnHandler) {
    super(instance, method, config, hasNext, returnHandler);
  }

  @Override
  public void handle(RoutingContext routingContext) {
    if (routingContext.response().ended()) {
      return;
    }
    if (routingContext.failed()) {
      return;
    }
    Object[] parameters;
    try {
      parameters = getParameters(routingContext);
    } catch (WrongParameterException e) { //NOSONAR
      DefaultErrorHandler.badRequest(routingContext, e.getMessage());
      return;
    }
    try {
      handleInvokation(routingContext, parameters);
    } catch (InvocationTargetException | IllegalAccessException ite) {
      LOG.error(ite);
      routingContext.fail(ite.getCause());
    }
  }

  @SuppressWarnings("unchecked")
  private void handleInvokation(RoutingContext routingContext, Object[] parameters) throws IllegalAccessException, InvocationTargetException {
    final T returned = (T) method.invoke(instance, parameters);
    if (returnsSomething) {
      handleMethodReturn(routingContext, returned);
    }
    if (!usesRoutingContext && hasNext) { // cannot call context.next(), assume the method is sync
      routingContext.next();
    }
    if (!usesRoutingContext && !usesHttpResponse && !hasNext) {
      sendResponse(routingContext);
    }
  }

  private void sendResponse(RoutingContext routingContext) {
    HttpServerResponse response = routingContext.response();
    try {
      response.setStatusCode(204);
      response.end();
    } catch (IllegalStateException ise) { //NOSONAR
      // do not log for the user, this means the response has already been written
      // that'd mean something is wrong with the **framework** not users' code
      routingContext.next();
    }
  }

  private void handleMethodReturn(RoutingContext routingContext, T returned) {
    final boolean contentTypeSet = routingContext.get(ContentTypeProcessor.BEST_CONTENT_TYPE) != null;
    if (returnHandler != null) {
      returnHandler.accept(routingContext, returned);
    } else if (hasNext && contentTypeSet) {
      // try to set as Payload
      Payload<Object> payload = routingContext.get(Payload.DATA_ATTR);
      if (payload == null) {
        payload = new Payload<>();
        routingContext.put(Payload.DATA_ATTR, payload);
      }
      payload.set(returned);
    } else if (returned instanceof String) {
      routingContext.response().end((String) returned);
    }
  }
}